/*
 * file network.c
 *
 * Hth's TCP Hijacker
 *
 * Hth networks
 * Copyright 2009
 * htt://www.unix-junkies.org
 * mail me at : hth at unix-junkies dot org
 *
 *
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the University of California, Berkeley nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "network.h"
#include "options.h"
#include "common.h"


static int get_ip_packet(pcap_t * pd, reply_t * reply, struct timeval * to);
static int select_loop(pcap_t * pd, struct timeval * to);


pcap_t * network_pcap_open(char * err) {
    char * device;
    pcap_t * pd;

    pd = NULL;
    if (o.iface) {
        device = o.iface;
    } else if ((device = pcap_lookupdev(err)) == NULL) {
        return NULL;
    }
    pd = pcap_open_live(device, 65535, 0, 200, err);
    if (pd) {
        pcap_setnonblock(pd, 1, err);
    }
    return pd;
}

int network_raw_sock_open(char * err) {
    int fd;

    /* this works only on Linux > 2.6.27 */
    if ((fd = socket(PF_INET, SOCK_RAW|SOCK_NONBLOCK, IPPROTO_ICMP)) == -1) {
        sprintf(err, "%s", strerror(errno));
    }
    return fd;
}

int receive_reply(pcap_t * pd, reply_t * reply, uint32_t msec) {
    struct timeval to_tv;

    to_tv.tv_sec  = msec / 1000;
    to_tv.tv_usec = (msec % 1000)*1000;

    reply->len = 0;
    reply->bytes = NULL;

    return get_ip_packet(pd, reply, &to_tv);
}

int get_ip_id(reply_t * reply) {
    assert(reply);
    if (reply->len < 20) {
        return -1;
    } else {
        struct iphdr * iph = (struct iphdr *)reply->bytes;
        return htons(iph->id);
    }
}

void set_pcap_filter(pcap_t * pd, char * filter) {
    struct bpf_program code;
    
    if (pd) {
        /* TODO : get netmask to ensure that filters involving broadcast addresses will work properly */
        if (pcap_compile(pd, &code, filter, 1, 0) < 0) {
            fatal("[!] Cannot compile the pcap filter : %s", pcap_geterr(pd));
        }
        if (pcap_setfilter(pd, &code) < 0) {
            fatal("[!] Cannot apply the pcap filter : %s", pcap_geterr(pd));
        }

        pcap_freecode(&code);
    }
}

int network_send_icmp_echo(int fd, struct in_addr dst) {
    uint8_t * pkt;
    size_t len;
    uint8_t datalen = 56;
    int err = 0;
    int i;
    struct sockaddr_in sin;

    len = sizeof(struct icmphdr) + datalen;
    if ((pkt = (uint8_t *)malloc(len)) == NULL) {
        return 1;
    } else {
        struct icmphdr *icmph = (struct icmphdr *)pkt;

        icmph->type     = ICMP_ECHO;
        icmph->code     = 0x00;
        icmph->checksum = 0x00;
        icmph->un.echo.id       = 0xf411;
        icmph->un.echo.sequence = 0x0001;

        for (i=0; i<datalen; i++) {
            uint8_t * d = pkt + sizeof(struct icmphdr) + i;

            *d = 'a' + i;
        }
        icmph->checksum = do_checksum((uint8_t *)icmph, len);
    }

    sin.sin_family  = AF_INET;
    sin.sin_port    = 0;
    sin.sin_addr    = dst;
    memset(&(sin.sin_zero), 0x00, sizeof(sin.sin_zero));

    if (sendto(fd, (const void *)pkt, len, 0, 
                (struct sockaddr *)&sin, sizeof(struct sockaddr_in)) < 0) {
        perror("sendto");
        err = 2;
    }

    free(pkt);
    return err;
}

uint16_t do_checksum(uint8_t * pck, size_t len) {
    register int sum = 0;
    uint16_t answer = 0;
    uint16_t * wp;

    for (wp = (uint16_t *)pck; len > 1; wp++, len -= 2)
        sum += *wp;

    if (len == 1) {
        *(uint8_t *)&answer = *(uint8_t *)wp;
        sum += answer;
    }

    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    answer = ~sum;
    return answer;
}
/*
 * -- --- -- --- INTERNAL STUFF --- -- --- --
 */
static int get_ip_packet(pcap_t * pd, reply_t * reply, struct timeval * to) {
    int ret = 1;
    char * bytes = NULL;
    struct pcap_pkthdr hdr;

    if (pcap_datalink(pd) != DLT_EN10MB)
        fatal("Non-ethernet support not implemented!");

    if (select_loop(pd, to) > 0) {
        bytes = (char *)pcap_next(pd, &hdr);
        if (bytes != NULL && hdr.caplen >= 42) {
            reply->len   = hdr.caplen - ETHERNET_HEADER_LENGTH;
            reply->bytes = bytes + ETHERNET_HEADER_LENGTH;
            ret = 0;
        }
    }
    return ret;
}

static int select_loop(pcap_t * pd, struct timeval * to) {
    int ret, fd;
    struct timeval timeout;
    fd_set fds;

    if ((fd = pcap_get_selectable_fd(pd)) == -1)
        fatal("Your system doesn't support select()ing on pcap descriptors");

    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    timeout = *to;

    do {
        errno = 0;
        ret = select(fd+1, &fds, NULL, NULL, to);
        if (ret == -1) {
            if (errno == EINTR) {
                fprintf(stderr, "Interrupted!\n");
                break;
            } else {
                perror("select");
                return -1;
            }
        }
    } while(ret == -1);

    return ret;
}

